package edu.cornell.cs4740.qa;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.store.LockObtainFailedException;

import edu.cornell.cs4740.qa.baseline.Answer;
import edu.cornell.cs4740.qa.baseline.Baseline;
import edu.cornell.cs4740.qa.baseline.OutputFormatter;
import edu.cornell.cs4740.qa.ir.DocumentQuery;
import edu.cornell.cs4740.qa.ner.NamedEntityRecognizer;
import edu.cornell.cs4740.qa.parsing.ParseDocsFile;
import edu.cornell.cs4740.qa.parsing.TokenFactory;
import edu.cornell.cs4740.qa.pos.PosTagger;
import edu.cornell.cs4740.qa.utilities.Util;

public class QA {
	private static String dataFolder = "";
	private static HashSet<String> whVerbs;
	private static final int numRelevantSentences = 100;

	private Question question; // current question to answer
	private List<Document> relevant_docs; // documents relevant question in rank

	// order

	public QA(Question question, List<Document> docs) {
		this.question = question;
		relevant_docs = docs;
		buildWhVerbsSet();
	}

	private void buildWhVerbsSet() {
		if (whVerbs == null) {
			whVerbs = new HashSet<String>();
			whVerbs.add("is");
			whVerbs.add("was");
			whVerbs.add("were");
			whVerbs.add("did");
			whVerbs.add("does");
			whVerbs.add("'s");
		}
	}

	private HashSet<String> howMuchIrrelevantWords;

	private HashSet<String> getHowMuchIrrelevantWords() {
		if (howMuchIrrelevantWords == null) {
			howMuchIrrelevantWords = new HashSet<String>();
			howMuchIrrelevantWords.add("how");
			howMuchIrrelevantWords.add("much");
			howMuchIrrelevantWords.add("many");
			howMuchIrrelevantWords.add("are");
			howMuchIrrelevantWords.add("in");
			howMuchIrrelevantWords.add("on");
			howMuchIrrelevantWords.add("from");
			howMuchIrrelevantWords.add("to");
			howMuchIrrelevantWords.add("and");
			howMuchIrrelevantWords.add("the");
		}
		return howMuchIrrelevantWords;
	}

	public List<Answer> answerQuestion() {
		List<Answer> possibleAnswers = findAnswers();
		return getTopFiveAnswers(possibleAnswers);
	}

	private List<Answer> findAnswers() {
		List<Answer> answers = new ArrayList<Answer>();
		Question.Type questionType = question.getQuestionType();
		switch (questionType) {
		case WHO:
			answers = getWhoAnswer(question);
			break;
		case WHAT:
			answers = getWhatAnswer(question);
			break;
		case WHERE:
			if(question.getQuestionNumber().equals(Util.walkThrough)){
				System.out.println("Type is labeled as type.WHERE, call getWhereAnswer(question)");
			}
			answers = getWhereAnswer(question);
			break;
		case WHEN:
			answers = getWhenAnswer(question);
			break;
		case HOW_MUCH:
			answers = getHowMuchAnswer(question);
			break;
		// case HOW_TO:
		// break;
		default:
			Baseline b = new Baseline(question, relevant_docs);
			answers = b.answerQuestion();
			break;
		}
		return answers;
	}

	private List<Answer> getWhoAnswer(Question q) {
		String properNameCode = "np";
		NamedEntityRecognizer ner = Util.getNer();

		// identify type of who question and key words
		Sentence s = q.getQuestionSentence();

		// "who was name?" and "who is name?" are description q's
		boolean descriptionQuestion = s.getWord(0).getWord()
				.equalsIgnoreCase("who")
				&& (s.getWord(1).getWord().equalsIgnoreCase("is") || s
						.getWord(1).getWord().equalsIgnoreCase("was"))
				&& s.getWord(2).getPos().equalsIgnoreCase(properNameCode);

		List<String> searchPhrase = getWhoKeyWords(s);

		// query accross docs
		DocumentQuery dq;
		List<String> relSent = null;
		try {
			dq = new DocumentQuery(relevant_docs, true);
			relSent = dq.getMostRelevantSentences(searchPhrase,
					numRelevantSentences);
		} catch (CorruptIndexException e) {
			System.out.println("CorruptIndexException");
			e.printStackTrace();
		} catch (LockObtainFailedException e) {
			System.out.println("LockObtainedFailedException");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("IOException");
			e.printStackTrace();
		}

		// tokenize and process relevant stuff for answers
		List<Answer> as = new ArrayList<Answer>();
		List<String> people = new ArrayList<String>();
		Map<String, String> nerMap;
		for (String st : relSent) {
			nerMap = ner.getEntitiesInSentence(st);
			for (Map.Entry<String, String> pair : nerMap.entrySet()) {
				if (pair.getValue().equals(NamedEntityRecognizer.PERSON_ENTITY)) {
					people.add(pair.getKey());
				}
			}
		}

		String docId = relevant_docs.get(0).getDocId();
		for (String p : people) {
			as.add(new Answer(q.getQuestionNumber(), docId, p));
		}

		return as;
	}

	private List<String> getWhoKeyWords(Sentence s) {
		List<String> relevantWords = new ArrayList<String>();
		if (s.getWord(1).getWord().equalsIgnoreCase("is")
				|| s.getWord(1).getWord().equalsIgnoreCase("was")) {
			for (int i = 2; i < s.getLength() - 1; i++) {
				if (i != 2 || !s.getWord(i).getWord().equalsIgnoreCase("the"))
					relevantWords.add(s.getWord(i).getWord());
			}
		} else {
			for (int i = 1; i < s.getLength() - 1; i++) {
				relevantWords.add(s.getWord(i).getWord());
			}
		}
		return relevantWords;
	}

	private List<Answer> getWhatAnswer(Question q) {
		Sentence s = q.getQuestionSentence();
		List<String> searchPhrase = getWhatKeyWords(s);

		DocumentQuery dq;
		List<String> relSent = null;
		try {
			dq = new DocumentQuery(relevant_docs, true);
			relSent = dq.getMostRelevantSentences(searchPhrase,
					numRelevantSentences);
		} catch (CorruptIndexException e) {
			System.out.println("CorruptIndexException");
			e.printStackTrace();
		} catch (LockObtainFailedException e) {
			System.out.println("LockObtainedFailedException");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("IOException");
			e.printStackTrace();
		}

		// tokenize and process relevant stuff for answers
		HashSet<String> nouns = new HashSet<String>();
		nouns.add("nn");
		nouns.add("nn$");
		nouns.add("nns");
		nouns.add("nns$");
		nouns.add("np");
		nouns.add("np$");
		nouns.add("nps");
		nouns.add("nps$");

		List<String> possibleAnswers = new ArrayList<String>();
		PosTagger posTagger = Util.getPosTagger();
		TokenFactory tokenFactory = new TokenFactory();
		String sTemp = "";
		boolean newAnswer = true;

		for (String st : relSent) {
			List<WordContainer> tokenList = tokenFactory.sentenceToTokens(st);
			posTagger.tagTokens(tokenList);
			Sentence relSentence = new Sentence(tokenList);
			for (WordContainer wc : relSentence.getAllWords()) {
				if (nouns.contains(wc.getPos())) {
					sTemp += (wc.getWord() + " ");
					newAnswer = false;
				} else if (!newAnswer) {
					possibleAnswers.add(sTemp);
					sTemp = "";
					newAnswer = true;
				}
			}
		}

		List<Answer> as = new ArrayList<Answer>();
		String docId = relevant_docs.get(0).getDocId();
		for (String p : possibleAnswers) {
			as.add(new Answer(q.getQuestionNumber(), docId, p));
		}
		return as;
	}

	private List<String> getWhatKeyWords(Sentence s) {
		List<String> relevantWords = new ArrayList<String>();
		for (int i = 1; i < s.getLength() - 1; i++) {
			String curWord = s.getWord(i).getWord();
			if (!whVerbs.contains(s.getWord(1).getWord().toLowerCase())
					|| !curWord.equalsIgnoreCase("the")) {
				relevantWords.add(curWord);
			}
		}
		return relevantWords;
	}

	private List<Answer> getWhereAnswer(Question q) {
		// Where is place?
		Sentence s = q.getQuestionSentence();
		List<String> searchPhrase = getWhereKeyWords(s);

		DocumentQuery dq;
		List<String> relSent = null;
		try {
			dq = new DocumentQuery(relevant_docs, true);
			if(question.getQuestionNumber().equals(Util.walkThrough)){
				System.out.println("Retrieving " + numRelevantSentences + " relevant sentences, in order of relevance from high to low, to the key words established earlier from all of the relevant documents using third party software Lucene");
			}
			relSent = dq.getMostRelevantSentences(searchPhrase,
					numRelevantSentences);
		} catch (CorruptIndexException e) {
			System.out.println("CorruptIndexException");
			e.printStackTrace();
		} catch (LockObtainFailedException e) {
			System.out.println("LockObtainedFailedException");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("IOException");
			e.printStackTrace();
		}

		// tokenize and process relevant stuff for answers
		NamedEntityRecognizer ner = Util.getNer();
		List<Answer> as = new ArrayList<Answer>();
		List<String> places = new ArrayList<String>();
		Map<String, String> nerMap;
		if(question.getQuestionNumber().equals(Util.walkThrough)){
			System.out.println("Using third party Stanford Named Entity Recognizer to label entities in each of the relevant sentences");
			System.out.println("Creating a list of all words and phrases labeled as a LOCATION_ENTITY");
		}
		for (String st : relSent) {
			nerMap = ner.getEntitiesInSentence(st);
			for (Map.Entry<String, String> pair : nerMap.entrySet()) {
				if (pair.getValue().equals(
						NamedEntityRecognizer.LOCATION_ENTITY)) {
					places.add(pair.getKey());
				}
			}
		}

		String docId = relevant_docs.get(0).getDocId();
		if(question.getQuestionNumber().equals(Util.walkThrough)){
			System.out.println("Creating answers for each of the LOCATOIN_ENTITY phrases found");
		}
		for (String p : places) {
			as.add(new Answer(q.getQuestionNumber(), docId, p));
		}

		return as;
	}

	private List<String> getWhereKeyWords(Sentence s) {
		if(question.getQuestionNumber().equals(Util.walkThrough)){
			System.out.println("Retrieving a list of key words in the question by removing words specific to where questions that may be in the question text");
		}
		List<String> relevantWords = new ArrayList<String>();
		if (whVerbs.contains(s.getWord(1).getWord().toLowerCase())) {
			for (int i = 2; i < s.getLength() - 1; i++) {
				String curWord = s.getWord(i).getWord();
				if (!curWord.equalsIgnoreCase("the")
						|| !curWord.equalsIgnoreCase("take")
						|| !curWord.equalsIgnoreCase("place")
						|| !curWord.equalsIgnoreCase("location")
						|| !curWord.equalsIgnoreCase("located")) {
					relevantWords.add(curWord);
				}
			}
		} else {
			for (int i = 1; i < s.getLength() - 1; i++) {
				relevantWords.add(s.getWord(i).getWord());
			}
		}
		return relevantWords;
	}

	private List<Answer> getWhenAnswer(Question q) {
		// Where is place?
		Sentence s = q.getQuestionSentence();
		List<String> searchPhrase = getWhenKeyWords(s);

		DocumentQuery dq;
		List<String> relSent = null;
		try {
			dq = new DocumentQuery(relevant_docs, true);
			relSent = dq.getMostRelevantSentences(searchPhrase,
					numRelevantSentences);
		} catch (CorruptIndexException e) {
			System.out.println("CorruptIndexException");
			e.printStackTrace();
		} catch (LockObtainFailedException e) {
			System.out.println("LockObtainedFailedException");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("IOException");
			e.printStackTrace();
		}

		// tokenize and process relevant stuff for answers
		NamedEntityRecognizer ner = Util.getNer();
		List<Answer> as = new ArrayList<Answer>();
		List<String> times = new ArrayList<String>();
		Map<String, String> nerMap;
		for (String st : relSent) {
			nerMap = ner.getEntitiesInSentence(st);
			for (Map.Entry<String, String> pair : nerMap.entrySet()) {
				if (pair.getValue().equals(NamedEntityRecognizer.DATE_ENTITY)
						|| pair.getValue().equals(
								NamedEntityRecognizer.TIME_ENTITY)) {
					times.add(pair.getKey());
				}
			}
		}

		String docId = relevant_docs.get(0).getDocId();
		for (String p : times) {
			as.add(new Answer(q.getQuestionNumber(), docId, p));
		}

		return as;
	}

	private List<String> getWhenKeyWords(Sentence s) {
		List<String> relevantWords = new ArrayList<String>();
		for (int i = 1; i < s.getLength() - 1; i++) {
			String curWord = s.getWord(i).getWord();
			if (!whVerbs.contains(s.getWord(1).getWord().toLowerCase())
					|| !curWord.equalsIgnoreCase("the")
					|| !curWord.equalsIgnoreCase("take")
					|| !curWord.equalsIgnoreCase("place")
					|| !curWord.equalsIgnoreCase("happen")
					|| !curWord.equalsIgnoreCase("occur")
					|| !curWord.equalsIgnoreCase("time")
					|| !curWord.equalsIgnoreCase("date")) {
				relevantWords.add(curWord);
			}
		}
		return relevantWords;
	}

	private List<Answer> getHowMuchAnswer(Question q) {
		Sentence s = q.getQuestionSentence();
		List<String> searchPhrase = getHowMuchKeyWords(s);
		HashSet<String> quantifiers = new HashSet<String>();
		quantifiers.add("cd");
		quantifiers.add("cd$");
		quantifiers.add("abn");
		quantifiers.add("abx");
		PosTagger posTagger = Util.getPosTagger();
		TokenFactory tokenFactory = new TokenFactory();

		boolean priceQuestion = false;
		for (WordContainer w : s.getAllWords()) {
			priceQuestion |= (w.getWord().equalsIgnoreCase("cost") || w
					.getStemmedWord().equalsIgnoreCase("cost"));
		}

		DocumentQuery dq;
		List<String> relSent = null;
		try {
			dq = new DocumentQuery(relevant_docs, true);
			relSent = dq.getMostRelevantSentences(searchPhrase,
					numRelevantSentences);
		} catch (CorruptIndexException e) {
			System.out.println("CorruptIndexException");
			e.printStackTrace();
		} catch (LockObtainFailedException e) {
			System.out.println("LockObtainedFailedException");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("IOException");
			e.printStackTrace();
		}

		// tokenize and process relevant stuff for answers
		// use ner and p-o-s for this
		NamedEntityRecognizer ner = Util.getNer();
		List<Answer> as = new ArrayList<Answer>();
		List<String> amounts = new ArrayList<String>();
		String sTemp = "";
		boolean newAnswer = true;
		Map<String, String> nerMap;
		for (String st : relSent) {
			nerMap = ner.getEntitiesInSentence(st);
			for (Map.Entry<String, String> pair : nerMap.entrySet()) {
				if (pair.getValue()
						.equals(NamedEntityRecognizer.PERCENT_ENTITY)
						&& (priceQuestion ? pair.getValue().equals(
								NamedEntityRecognizer.MONEY_ENTITY) : true)) {
					amounts.add(pair.getKey());
				}
			}
			List<WordContainer> tokenList = tokenFactory.sentenceToTokens(st);
			posTagger.tagTokens(tokenList);
			Sentence relSentence = new Sentence(tokenList);
			for (WordContainer wc : relSentence.getAllWords()) {
				if (quantifiers.contains(wc.getPos())) {
					sTemp += (wc.getWord() + " ");
					newAnswer = false;
				} else if (!newAnswer) {
					if (wc.getPos().equals("nn") || wc.getPos().equals("nns"))
						sTemp += (wc.getWord());
					amounts.add(sTemp);
					sTemp = "";
					newAnswer = true;
				}
			}
		}

		String docId = relevant_docs.get(0).getDocId();
		for (String p : amounts) {
			as.add(new Answer(q.getQuestionNumber(), docId, p));
		}

		return as;
	}

	private List<String> getHowMuchKeyWords(Sentence s) {
		List<String> keyWords = new ArrayList<String>();
		HashSet<String> irrWords = getHowMuchIrrelevantWords();
		for (WordContainer wc : s.getAllWords()) {
			if (!irrWords.contains(wc.getWord().toLowerCase())) {
				keyWords.add(wc.getWord());
			}
		}
		return keyWords;
	}

	private List<Answer> getTopFiveAnswers(List<Answer> possibleAnswers){
		List<Answer> finals = new ArrayList<Answer>();
		int i=0;
		HashSet<Answer> uniques = new HashSet<Answer>();
		if(question.getQuestionNumber().equals(Util.walkThrough)){
			System.out.println("Removing all double answers (answers whose text are exactly identical)");
		}
		while(i<possibleAnswers.size()&&finals.size()<5){
			if(!uniques.contains(possibleAnswers.get(i))){
				uniques.add(possibleAnswers.get(i));
				finals.add(possibleAnswers.get(i));
			}
			i++;
		}
		while(finals.size()<5){
			finals.add(new Answer(question.getQuestionNumber(),
						relevant_docs.get(0).getDocId(), "No Answer Found"));
		}
		if(question.getQuestionNumber().equals(Util.walkThrough)){
			System.out.println("Because sentences and answers are ordered ranked on their relevance according to Lucene, first 5 answers are chosen. \"No Answer\" answers are created if there are not at least 5");
		}
		return finals;
	}
	
//	private List<Answer> getTopFiveAnswers(List<Answer> possibleAnswers) {
//		List<Answer> finals = new ArrayList<Answer>();
//		if (possibleAnswers.size() > 5) {
//			HashSet<String> uniques = new HashSet<String>();
//			HashMap<String, Answer> sToA = new HashMap<String, Answer>();
//			int i = 0;
//			while (uniques.size() < 5 && i < possibleAnswers.size()) {
//				if (!uniques.contains(possibleAnswers.get(i).getAnswerText())) {
//					uniques.add(possibleAnswers.get(i).getAnswerText());
//					sToA.put(possibleAnswers.get(i).getAnswerText(),
//							possibleAnswers.get(i));
//				}
//				i++;
//			}
//			for (String s : uniques) {
//				finals.add(sToA.get(s));
//			}
//			while (finals.size() < 5) {
//				finals.add(new Answer(question.getQuestionNumber(),
//						relevant_docs.get(0).getDocId(), "No Answer Found"));
//			}
//			return finals;
//		} else {
//			while (possibleAnswers.size() < 5) {
//				possibleAnswers.add(new Answer(question.getQuestionNumber(),
//						relevant_docs.get(0).getDocId(), "No Answer Found"));
//			}
//			return possibleAnswers;
//		}
//	}

	/**
	 * 
	 * @param args
	 *            [0] question file path [1] docs common file path (.###) will
	 *            be added to each for question ### [2] answer file path if
	 *            available
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		String questionFilePath = args[0];
		List<Question> questions = ParseDocsFile
				.parseQuestionFile(questionFilePath);
		OutputFormatter baseline_output = new OutputFormatter();
		OutputFormatter qa_output = new OutputFormatter();
		List<Answer> correctAnswers = null;
		if (args.length > 2) {
			correctAnswers = ParseDocsFile.parseAnswerFile(args[2]);
		}

		for (int i = 0; i < questions.size(); i++) {
			Question question = questions.get(i);
			String questionNum = question.getQuestionNumber();
			List<Document> docs = ParseDocsFile.parseDocsFiles(args[1] + "."
					+ questionNum);

			Baseline b = new Baseline(question, docs);
			List<Answer> an = b.answerQuestion();
			if (an.size() == 0) {
				an.add(new Answer(questionNum, "", ""));
			}
			baseline_output.submitAnswerList(an);

			QA qa = new QA(question, docs);
			qa_output.submitAnswerList(qa.answerQuestion());
		}

		try {
			baseline_output.generateFile(new File("baseline_dev.txt"));
		} catch (IOException e) {
			System.err
					.println("baseline_output.txt failed to be written properly.");
			e.printStackTrace();
		}

		try {
			qa_output.generateFile(new File("qa_dev.txt"));
		} catch (IOException e) {
			System.out.println("qa_output.txt failed to be written properly.");
			e.printStackTrace();
		}

		if (correctAnswers != null) {
			Util.compareAnswers(correctAnswers, baseline_output.getAnswers(),
					"baseline_results.txt");
			Util.compareAnswers(correctAnswers, qa_output.getAnswers(),
					"qa_results.txt");
		}
	}

	public static String getDataFolder() {
		return dataFolder;
	}

}
